﻿namespace SsisLdapSource.DirectoryServicesProtocolsExtensions
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.DirectoryServices.Protocols;

    public static class SearchResultToDataTable
    {
        private static readonly Dictionary<string, LdapAttributeMetaMap> Cache = new Dictionary<string, LdapAttributeMetaMap>();
        
        public static void ToDataTable(
          this SearchResultEntry searchResultEntry,
          LdapConnection ldapConnection,
          LdapMetaMapFactory ldapMetaMapFactory,
          DataTable resultDataTable,
          string primaryKey)
        {
            if (resultDataTable.Columns[primaryKey] == null)
            {
                throw new ArgumentNullException(
                    "resultDataTable",
                    "LdapPrimaryKeyEquivalentAttribute is a mandatory field, but was not found in the resulting data table");
            }
            
            if (searchResultEntry.Attributes.Values == null)
            {
                return;
            }

            var searchResultParser = new SearchResultParser(ldapConnection);
            var searchResultContainsOnlyMultiValuedAttributes = true;
            var singleValuedRow = resultDataTable.NewRow();
            var primaryKeyMetaMapping = GetLdapToDotNetMetaMap(ldapConnection, ldapMetaMapFactory, primaryKey);
            var primaryKeyValue = GetPrimaryKeyValue(searchResultEntry, primaryKey, primaryKeyMetaMapping);

            foreach (DirectoryAttribute attribute in searchResultEntry.Attributes.Values)
            {
                var ldapToDotNetMetaMap = GetLdapToDotNetMetaMap(ldapConnection, ldapMetaMapFactory, attribute.Name);

                if (!ldapToDotNetMetaMap.IsMultiValued)
                {
                    searchResultContainsOnlyMultiValuedAttributes = false;
                    singleValuedRow[attribute.Name] = searchResultParser.GetSingleValuedAttributeData(
                        ldapToDotNetMetaMap,
                        attribute);
                }
                else
                {
                    var multiValuedData = searchResultParser.GetMultiValuedAttributeData(
                        ldapToDotNetMetaMap,
                        attribute,
                        searchResultEntry.DistinguishedName);
                    

                    foreach (var attributeValue in multiValuedData)
                    {
                        var multiValuedRow = resultDataTable.NewRow();
                        multiValuedRow[primaryKey] = primaryKeyValue;
                        multiValuedRow[
                            SearchResultParser
                            .StripExpansionDirectiveFromAttributeNameIfPresent(attribute.Name)] 
                            = attributeValue;
                        resultDataTable.Rows.Add(multiValuedRow);
                    }
                }
            }

            if (!searchResultContainsOnlyMultiValuedAttributes)
            {
                singleValuedRow[primaryKey] = primaryKeyValue ;
                resultDataTable.Rows.Add(singleValuedRow);
            }
        }

        private static LdapAttributeMetaMap GetLdapToDotNetMetaMap(LdapConnection ldapConnection,
                                                                   LdapMetaMapFactory ldapMetaMapFactory,
                                                                   string attributeName)
        {
            // Network Call - Causes perf issues with large queires, hence adding caching
            LdapAttributeMetaMap ldapToDotNetMetaMap;
            if (Cache.ContainsKey(attributeName))
            {
                ldapToDotNetMetaMap = Cache[attributeName];
            }
            else
            {
                ldapToDotNetMetaMap = ldapConnection.GetAttributeMetaData(
                    SearchResultParser.StripExpansionDirectiveFromAttributeNameIfPresent(attributeName),
                    ldapMetaMapFactory);
                try
                {
                    Cache.Add(attributeName, ldapToDotNetMetaMap);
                }
                catch (ArgumentException)
                {
                } // it is possible that another thread added the same object to cache, so safe to ignore this
            }
            return ldapToDotNetMetaMap;
        }

        private static object GetPrimaryKeyValue(
            SearchResultEntry searchResultEntry,
            string primaryKey,
            LdapAttributeMetaMap primaryKeyMetaMapping)
        {
            object primaryKeyValue;
            if (primaryKeyMetaMapping.SemanticValueConversionFunction != null)
            {
                primaryKeyValue =
                    primaryKeyMetaMapping.SemanticValueConversionFunction(
                        searchResultEntry.Attributes[primaryKey][0]);
            }
            else
            {
                primaryKeyValue =
                    searchResultEntry.Attributes[primaryKey].GetValues(primaryKeyMetaMapping.RawDotNetTypeEquivalent)[0];
            }
            return primaryKeyValue;
        }
    }
}
